package jp.co.tms.internal.runserver;

import java.io.IOException;
import java.net.BindException;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;

import jp.co.tms.internal.data.IRunServer;
import jp.co.tms.internal.data.RunServer;
import jp.co.tms.internal.runserver.exception.ConnectionIsInUseException;
import jp.co.tms.internal.runserver.exception.ConnectionIsOutException;
import jp.co.tms.internal.runserver.exception.NoHandleException;
import jp.co.tms.internal.runserver.exception.PortNumberIsUnLegalException;
import jp.co.tms.internal.runserver.protocol.HandshakePackage;
import jp.co.tms.internal.runserver.protocol.IFinishPackage;
import jp.co.tms.internal.runserver.protocol.IHandshakeAckPackage;
import jp.co.tms.internal.runserver.protocol.IPackage;
import jp.co.tms.internal.runserver.protocol.IRunAckPackage;
import jp.co.tms.internal.runserver.protocol.RunPackage;
import jp.co.tms.internal.runserver.util.PackageUtil;
import jp.co.tms.internal.socket.ISocketEventHandle;
import jp.co.tms.internal.socket.RunServerSocket;
import jp.co.tms.internal.task.IRunTask;

public class RunServerCtrl implements ISocketEventHandle {
	// port number
	private final static int DEFAULT_PORT = 9001;
	// socket
	private RunServerSocket _socket = new RunServerSocket(this);
	// client list
	private ArrayList<IRunServer> _servers = new ArrayList<IRunServer>();
	// event engine.
	private IRunServerEventHandle _eventEngine = null;
	
	public RunServerCtrl(IRunServerEventHandle handle){
		this._eventEngine = handle;
	}
	
	public void run() throws
	NoHandleException,
	ConnectionIsInUseException,
	PortNumberIsUnLegalException,
	ConnectionIsOutException,
	BindException,
	IOException{
		//
		// No handle
		if (_eventEngine == null)
			throw new NoHandleException();
		
		//
		// bind the port
		_socket.bind(DEFAULT_PORT);
		//
		// start listening the client. 
		_socket.listen();
	}
	
	public void startTask(IRunTask task){
		IRunServer server = getIdleServer();
		if (server == null 
				|| !server.hasIdleCpu()){
			return ;
		}
		
		task.setRunServer(server);
		task.setStatus(IRunTask.STAT_RUNNING);
		server.addTaskCount();
		//
		// send the run task package.
		sendRunTsakPackage(server,
				task.getId(), 
				task.getCommand());
	}
	
	public boolean hasIdleCpu(){
		return getIdleServer() == null ? false : true;
	}

	@Override
	public void processData(SocketChannel sc, byte[] msg) {
		IPackage data = PackageUtil.getPackage(msg);
		if (data == null)
			return;
		
		if (data instanceof IHandshakeAckPackage){
			IHandshakeAckPackage packageData = (IHandshakeAckPackage)data;
			
			//
			// check the server list, if the server has already in the list,
			// just update the data.if it is not in the list, create a new one.
			boolean isNewServer = true;
			int serverId = packageData.getServerId();
			for ( IRunServer server : _servers ){
				if ( server.getServerId() == serverId ){
					server.setCpus(packageData.getCpus());
					server.setSocketChannel(sc);
					server.setEnable(true);
					isNewServer = false;
				}
			}
			// if it is not in the list.
			if (isNewServer == true){
				_servers.add(new RunServer(sc, 
						serverId, 
						packageData.getCpus()));
			}
		}
		else if ( data instanceof IRunAckPackage ){
			IRunAckPackage packageData = (IRunAckPackage)data;
			if (_eventEngine != null ){
				_eventEngine.onTaskRunning( packageData.getId() );
			}
		}
		else if ( data instanceof IFinishPackage ){
			IFinishPackage packageData = (IFinishPackage)data;
			if (_eventEngine != null 
					&& _eventEngine.onTaskFinish( packageData.getId() )){
				IRunServer server = getServer(sc);
				if (server == null)
					return;
				// remove the task from task list.
				server.remTaskCount();
			}	
		}
		else{
			return;
		}
	}

	@Override
	public void connect(SocketChannel sc) {
		//
		// For Debug
		try{
			System.out.println("CONNECTED " + sc.getRemoteAddress().toString());
		}
		catch(IOException e){
		}
		
		//
		// Send handshake package to the remote machine.
		sendHandshakePackage(sc);
	}

	@Override
	public void disconnect(SocketChannel sc) {
		//
		// For Debug
		try{
			System.out.println("DISCONNECTED " + sc.getRemoteAddress().toString());
		}
		catch (IOException e){
		}
		
		// 1. Change the status of the run server list. 
		//    We do not remove the run server's data because it is marked by 
		//    The ID, maybe it will contact us later, then we can rebuild
		//    the server's socket info.
		for (IRunServer server : _servers){
			if (server.getSocketChannel().equals(sc)){
				server.setEnable(false);
			}
		}
	}
	
	protected IRunServer getServer(SocketChannel sc){
		for (IRunServer server : _servers){
			if (server.getSocketChannel().equals(sc)){
				return server;
			}
		}
		
		return null;
	}
	
	protected IRunServer getIdleServer(){
		if (_servers.isEmpty())
			return null;
		
		for (IRunServer server : _servers){
			if (server.getCpus() > server.getTaskCount())
				return server;
		}
		
		return null;
	}
	
	protected void sendRunTsakPackage(IRunServer server, int id, String command){
		_socket.send(server.getSocketChannel(), new RunPackage(id, command).getBytes());
	}
	
	protected void sendHandshakePackage(SocketChannel sc){
		_socket.send(sc, new HandshakePackage().getBytes());	
	}
}
